package com.filter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.zip.GZIPOutputStream;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.WriteListener;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
 
//整站压缩过滤器
@WebFilter(filterName="zipFilter" ,urlPatterns="/*")
public class GzipFilter implements Filter{
     
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {     
         
        HttpServletRequest request = (HttpServletRequest)req ;
        HttpServletResponse response = (HttpServletResponse)res ;     
        //重新封装response对象,来达到改写其方法的过程
        GzipHttpServletResponse gzipResponse = new GzipHttpServletResponse(response) ;
        //以上为过滤器的请求时过滤过程
        //当请求时、传入的response对象的getOutputStream方法已经修改过
        ////因此，此时如果页面上调用getOutPutStream 方法的时候，其实调用的是自己写的。
        chain.doFilter(request, gzipResponse) ;
        //请求结束， 开始响应，二次输出的时候，就是开始要将内存中的字节数组，经过压缩传输到客户端的过程了。
        // 取出数据原始字节
        byte[] b = gzipResponse.getOldBytes() ;
        System.out.println("过滤器中原始数据大小="+b.length);
        
        // 创建内存缓冲字节流
        ByteArrayOutputStream baos = new ByteArrayOutputStream() ;
        // 把数据压缩到缓冲字节流中
        GZIPOutputStream out = new GZIPOutputStream(baos) ;
        out.write(b) ;
        out.close() ;       
         
        // 取出压缩后的数据
        b = baos.toByteArray() ;
        System.out.println("过滤器中压缩后的数据大小="+b.length);
        // 告知客户端压缩格式和文件长度
        response.setHeader("Content-Encoding", "gzip");
        response.setContentLength(b.length) ;      
        response.getOutputStream().write(b) ;
    }   
    public void destroy() {}
    public void init(FilterConfig arg0) throws ServletException {  }
}
class GzipHttpServletResponse extends HttpServletResponseWrapper{
	 //用来存放页面要输出信息的字节数组。
    private ByteArrayOutputStream baos = new ByteArrayOutputStream() ;
    //封装的字符流输出对象
    private PrintWriter pw ;
     
    public GzipHttpServletResponse(HttpServletResponse response) {
        super(response);        
    }
 
    //这个方法，就是要重写的方法，该方法有返回值ServletOutputStream，
    //而该类又是个抽象类，所以必须还要把这个类给实现。可以参考下一个类 
   @Override
    public ServletOutputStream getOutputStream() throws IOException {
        return new MyServletOutStream(baos) ;
    }   
    
    @Override
    public PrintWriter getWriter() throws IOException {
        pw = new PrintWriter(new OutputStreamWriter(baos, super.getCharacterEncoding())) ;
        return pw ;
    }
 
    //该方法很重要，用来获取页面信息存放在内存中的字节数组。 也是压缩的数据
    public byte[] getOldBytes(){        
    	try {
			if(pw!=null){
				pw.flush();
				pw.close();
			}
			baos.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return baos.toByteArray();
    }
     
}
//这个是应上面getOutPutStream 的邀请的返回值。 也是要 servlet中实际调用的方法。    
//不会输出到页面，只会将servlet中要写的内容放入到内存中去。
class MyServletOutStream extends ServletOutputStream{
    private ByteArrayOutputStream baos ; //缓冲页面数据存放区域
    public MyServletOutStream(ByteArrayOutputStream baos){
        this.baos = baos ;
    }
    @Override
    public void write(int b) throws IOException {
    	System.out.println("MyServletOutStream====write方法");
        baos.write(b) ;//将输出写入输出缓存
    }
	@Override
	public boolean isReady() {
		return false;
	}
	@Override
	public void setWriteListener(WriteListener arg0) {
	}
}